// -*- C++ -*-
#ifndef SDATAVIEW_H
#define SDATAVIEW_H

#include <vector>
#include <numeric>
#include <functional>
#include <cassert>

#include "sdatatype.h"
#include "sutility.h"

namespace sds {

  template <typename Type>
  class SDataView;

  template <typename Type>
  class SDataViewProxy;

  template <typename Type>
  class SDataViewConstProxy;


  template <typename Type>
  class SDataView {
    friend class SDataViewProxy<Type>;
    friend class SDataViewConstProxy<Type>;
    friend class SDataset;

  public:

    typedef std::vector<Type> Data;

    SDataView() {}

    SDataView(const std::vector<SInteger>& size) 
      : _size(size), _data(std::accumulate(size.begin(), size.end(), 
					  1, std::multiplies<int>())) {}

    SDataView(const std::vector<SInteger>& size, const Type& value) 
      : _size(size), _data(std::accumulate(size.begin(), size.end(), 
					   1, std::multiplies<int>()), value) {}
  
    SDataViewProxy<Type> operator[](int index) {
      return SDataViewProxy<Type>(*this, index);
    }

    SDataViewConstProxy<Type> operator[](int index) const {
      return SDataViewConstProxy<Type>(*this, index);
    }

    typename std::vector<Type>::reference operator[]
    (const std::vector<SInteger>& index) {
      int offset = 0;
      assert(index.size() == _size.size());
      for (int i = 0; i < index.size(); ++i) {
	offset = offset * _size[i] + index[i];
      }
      return _data[offset];
    }

    typename std::vector<Type>::const_reference operator[]
    (const std::vector<SInteger>& index) const {
      int offset = 0;
      assert(index.size() == _size.size());
      for (int i = 0; i < index.size(); ++i) {
	offset = offset * _size[i] + index[i];
      }
      return _data[offset];
    }
    

    const std::vector<SInteger>& getSize() const { return _size;}

    void reSize(const std::vector<SInteger>& size) {
      _size = size;
      _data.resize(std::accumulate(size.begin(), size.end(), 
				   1, std::multiplies<int>()));
    }

    const Data& data() const { return _data; }
    Data& data() { return _data; }

    void swap(SDataView<Type>& swp) {
      _size.swap(swp._size);
      _data.swap(swp._data);
    }

  protected:
  
    std::vector<SInteger> _size;
    std::vector<Type> _data;
  };


  template <typename Type>
  class SDataViewProxy {
    friend class SDataView<Type>;
  public:

    SDataViewProxy operator[](int index) {
      assert(!isValue());
      int newOffset = _offset * _data_view._size[_depth + 1] + index;
      return SDataViewProxy(_data_view, newOffset, _depth + 1);
    }

    operator typename std::vector<Type>::const_reference() const {
      assert(isValue());
      return _data_view._data[_offset]; 
    }

    operator typename std::vector<Type>::reference() {
      assert(isValue());
      return _data_view._data[_offset]; 
    }

    typename std::vector<Type>::reference operator=(const Type& type) {
      assert(isValue());
      _data_view._data[_offset] = type;
      return _data_view._data[_offset]; 
    }

    typename std::vector<Type>::reference 
      operator=(const SDataViewProxy& proxy) {
      assert(isValue());
      _data_view._data[_offset] = 
	(typename std::vector<Type>::const_reference)proxy;
      return _data_view._data[_offset]; 
    }

  private:

    bool isValue() const {
      return (int)_data_view._size.size() == _depth + 1;
    }

    SDataViewProxy(SDataView<Type>& dataView, int index, int depth = 0) 
      : _data_view(dataView), _offset(index), _depth(depth) {}

    SDataView<Type>& _data_view;
    int _offset;
    int _depth;
  };


  template <typename Type>
  class SDataViewConstProxy {
    friend class SDataView<Type>;
  public:

    SDataViewConstProxy operator[](int index) const {
      assert(!isValue());
      int newOffset = _offset * _data_view._size[_depth + 1] + index;
      return SDataViewConstProxy(_data_view, newOffset, _depth + 1);
    }

    operator typename std::vector<Type>::const_reference() const {
      assert(isValue());
      return _data_view._data[_offset]; 
    }

  private:

    bool isValue() const {
      return (int)_data_view._size.size() == _depth + 1;
    }

    SDataViewConstProxy(const SDataView<Type>& _data_view, 
			int index, int depth = 0) 
      : _data_view(_data_view), _offset(index), _depth(depth) {}

    const SDataView<Type>& _data_view;
    int _offset;
    int _depth;
  };
}

#endif
